#include <fs/vfs.h>
#include <klibc/string.h>
#include <klibc/ctype.h>
#include <klibc/malloc.h>
#include <klibc/stdio.h>
#include <system/config.h>
#include "devfs/devfs.h"

/*
 * Inizializza il vfs
 */
void vfs_init()
{
	devfs_init();
	vfs_root = NULL;
	vfs_mounted = NULL;
}

/*
 * Restituisce il nodo richiesto nel path
 */
vfs_node_t *vfs_get_node(char *path)
{
	if(strcmp("/\0", path) == 0) return vfs_root;
	path++;
	
	// Troviamo il mount point
	vfs_node_t *mount_p = vfs_get_mount_point(path);
	
	// Se non esiste, vuol dire che non esiste manco il path richiesto
	if(mount_p == NULL)
		return NULL;

	// Allochiamo il nodo e attribuiamogli le caratteristiche del filesystem
	vfs_node_t *nodo = (vfs_node_t *) malloc(sizeof(vfs_node_t));
	*nodo = *mount_p;
	
	// Ora *path deve iniziare da dopo il mount point
	path = path + strlen(mount_p->name) + 1;
	
	// Scorriamo recursivamente le dir fino a trovare il nodo finale
	vfs_node_t *temp = mount_p;
	vfs_node_t *prev;
	char cur_name[VFS_NODE_NAME_LEN];
	uint32_t len = 0;
	
	while(*path != '\0')
	{
		cur_name[len] = *path;
		//printf("%s\n", path);
		len++;
		path++; 
		
		if(*path == '/' || *path == '\0')
		{
			cur_name[len] = '\0';
			len = 0;
			//printf("New node layer: %s\n",cur_name);
			//printf("%s\n",cur_name);
			//prev = temp;
			temp = temp->finddir(temp, cur_name);
			//if(prev != mount_p) free(prev);
			//printf("%s\n", temp->name);
		}
	}
	
	return temp;
}


/*
 * Dato un path, restituisce il mountpoint
 */
vfs_node_t *vfs_get_mount_point(char *path)
{
	char *fs_name;
	char *path_name;
	int32_t longest = 0;
	int32_t cur_long;
	vfs_node_t *mount_point = vfs_mounted;
	vfs_node_t *last = vfs_root;
	
	while(mount_point != NULL)
	{
		cur_long = 0;
		path_name = path;
		fs_name = mount_point->name;
		
		while(*fs_name != '\0' && *path_name != '\0')
		{
			if(*fs_name != *path_name)
				break;
				
			fs_name++; path_name++; cur_long++;
		}
		
		if(*fs_name == '\0')
		{
			if(cur_long > longest)
			{
				longest = cur_long;
				last = mount_point;
			}
		}		
		
		mount_point = mount_point->next;
	}
	
	return last;	
}


/*
 * Aggiunge un filesystem alla lista dei filesystem disponibili
 */
void vfs_add_fs(vfs_node_t *fs)
{
	inode_list_add(&vfs_fs_list, fs);
}

/*
 * Aggiunge un filesystem alla lista dei filesystem disponibili
 */
uint8_t vfs_check_fs(char *fs)
{
	return inode_list_check(vfs_fs_list, fs);
}


/*
 * 
 */
uint8_t vfs_umount(char *path)
{
	if(strcmp(path, "/dev") == 0) 
		return VFS_ER_UMOUNT_DEV;
		
	if(strlen(path) > 2)
		*path++;
		
	if(inode_list_del(&vfs_mounted, path))
		return VFS_OK;
	else
		return VFS_ER_UMOUNT_NOT_PRESENT;
}

uint8_t vfs_allumount()
{
	return inode_list_clear(&vfs_mounted);
}

/*
 * Mount a node on custom dir
 */
uint32_t vfs_mount(char *fs, char *devpath, char *path)
{
	// Aggiungere l'umount ricorsivo 
	vfs_node_t *f = vfs_fs_list;
	
	while(f != NULL)
	{
		if(strcmp(f->name, fs) == 0)
		{
			vfs_node_t *fs_cur = (vfs_node_t *) malloc(sizeof(vfs_node_t));
			*fs_cur = *f;
			
			path++;
			strcpy(fs_cur->name, path);
			if(strcmp(fs_cur->name, "\0") == 0) strcpy(fs_cur->name, "/\0");
			strcat(fs_cur->name, "\0");
			
			
			//printf("%s\n\n",fs_cur->name);
			if(devpath != 0 && vfs_get_node(devpath) != NULL)
			{
				strcpy(fs_cur->dev_path, devpath);
			}
			
			
			fs_cur->fs = f;
				
			
			// Solo / e dev possono essere astratti, gli altri devono essere per forza montati su cartelle che esistono
			if(
				(vfs_get_node(fs_cur->name) == NULL && strcmp(fs_cur->name, "dev\0") != 0 && strcmp(fs_cur->name, "/\0") != 0)
				||
				(devpath != 0 && vfs_get_node(devpath) == NULL))
			{
				free(fs_cur);
				return 0;
			}
			
			fs_cur->type = VFS_NODE_TYPE_MOUNT;
			inode_list_add(&vfs_mounted, fs_cur);
			
			/*if(devpath != 0)
				printf("\nmounted '%s' with '%s' filesystem on '%s'\n", devpath, fs, vfs_mounted->name);
			else
				printf("\nmounted '%s' filesystem on '%s'\n", fs, vfs_mounted->name);
			*/
			return 1;
		}
	
		f = f->next;
	}
	
	return 0;
}

/*
 * Visualizza l'albero dei nodi
 */
void vfs_show_tree(vfs_node_t *parent, uint32_t level)
{
	if(level == 0)
	{
		video_color_puts(THEME_START_COLOR, THEME_START_MSG);
		printf("Tree viewing vfs:\n");
	}
	
	char strc[VFS_NODE_NAME_LEN];
	uint32_t x = 0;
	uint32_t y;
	x = 0;
	//printf("%d\n",x);
	while(vfs_readdir(parent, strc, x))
	{
		vfs_node_t *child;
		//printf("\t\t%s\n\n",parent->name);
		if(child = vfs_finddir(parent, strc))
		{
			printf("   ");
			for(y = 0; y < level; y++) printf("  ");
			printf("/%s\n", child->name);
				
			if(child->type == VFS_NODE_TYPE_DIR || child->type == VFS_NODE_TYPE_MOUNT){ vfs_show_tree(child, (level+1)); }
		}
		x++;			
	}		
}


/*
 * Funzioni del virtual fs
 */
uint32_t vfs_read(vfs_node_t *node, char *data, uint32_t size, uint64_t off)
{
	if(node != NULL && node->read != NULL)
		return node->read(node, data, size, off);
	
	return 0;
}

uint32_t vfs_write(struct vfs_node_s *node, char *data, uint32_t size, uint64_t off)
{
	if(node != NULL && node->write != NULL)
		return node->write(node, data, size, off);
	
	return 0;
}

void vfs_open(struct vfs_node_s *node)
{
	if(node != NULL && node->open != NULL)
		node->open(node);	
}

void vfs_close(struct vfs_node_s *node)
{
	if(node != NULL && node->close != NULL)
		node->close(node);		
}


uint8_t vfs_readdir(vfs_node_t *node, char *name, uint32_t num)
{
	if(num == 0 && node == vfs_root)
	{
		strcpy(name, "dev\0");
		return 1;
	}
	
	if(node != NULL && node->readdir != NULL)
		return node->readdir(node, name, num);
}

vfs_node_t *vfs_finddir(vfs_node_t *node, char *name)
{
	if(node == vfs_root && strcmp(name, "dev\0") == 0) 
	{
		//printf("finddir mount: %s\n",vfs_get_mount_point("dev")->name);
		return vfs_get_mount_point("dev");
	}
	
	if(node != NULL && node->finddir != NULL)
		return node->finddir(node, name);
}




void vfs_mount_command()
{
	int i = 0;
	
	
	switch(argc)
	{
		case 2:
			if(strcmp(argv[1], "--list") == 0 || strcmp(argv[1], "-l") == 0)	
			{
				vfs_node_t *parent = vfs_mounted;
		
				video_color_puts(THEME_START_COLOR, THEME_START_MSG);
				printf("Mounted fs:\n");
				
				while(parent != NULL)
				{
					printf("\t%s (fs: %s) (dev: %s)\n", parent->name, parent->fs->name, parent->dev_path);
				
					parent = parent->next;
				}	
			}
			break;
			
		case 5:
			if(strcmp(argv[1], "--mount") == 0 || strcmp(argv[1], "-m") == 0)	
			{
				if(vfs_mount(argv[2], argv[3], argv[4]))
				{
					printf("mounted device '%s' with fs '%s' on dir '%s'\n", argv[2], argv[3], argv[4]);
				}
				else
				{
					printf("cannot mount device '%s' with fs '%s' on dir '%s'\n", argv[2], argv[3], argv[4]);
				}
			}
			break;			
		
		case 3:
			if(strcmp(argv[1], "--umount") == 0 || strcmp(argv[1], "-u") == 0)	
			{
				int er = vfs_umount(argv[2]);
				if(er == VFS_OK)
				{
					printf("umounted %s\n", argv[2]);
				}
				else
				{
					printf("cannot umount %s because ", argv[2]);
					
					if(er == VFS_ER_UMOUNT_DEV) printf("you're trying to umount /dev\n");
					else if(er == VFS_ER_UMOUNT_NOT_PRESENT) printf("you're trying to umount a invalid mountpoint\n");
				}
			}
			break;
	}
	//printf("%d\n",argc);
}

